package moltencore.network.protocol;

import moltencore.game.Game;
import moltencore.network.serverlobby.GameData;
import spark.network.Util;
import spark.network.protocol.Base;

public class GameListGameData extends Base {
	public static final short TYPE = 0x16;
	private static final byte[] TYPE_BYTES = Util.makeBytes(TYPE);

	/**
	 * the game's port number
	 */
	private int port;
	private static final int PORT_OFFSET = Base.SIZE;
	private static final int PORT_LENGTH = Integer.SIZE / 8;

	/**
	 * current player count
	 */

	private byte currentPlayerCount;
	private static final int CURRENT_PLAYER_COUNT_OFFSET = PORT_OFFSET + PORT_LENGTH;
	private static final int CURRENT_PLAYER_COUNT_LENGTH = Byte.SIZE / 8;

	/**
	 * maximum player count
	 */

	private byte maximumPlayerCount;
	private static final int MAXIMUM_PLAYER_COUNT_OFFSET = CURRENT_PLAYER_COUNT_OFFSET + CURRENT_PLAYER_COUNT_LENGTH;
	private static final int MAXIMUM_PLAYER_COUNT_LENGTH = Byte.SIZE / 8;

	/**
	 * normal bubble count
	 */

	private byte normalBubbleCount;
	private static final int NORMAL_BUBBLE_COUNT_OFFSET = MAXIMUM_PLAYER_COUNT_OFFSET + MAXIMUM_PLAYER_COUNT_LENGTH;
	private static final int NORMAL_BUBBLE_COUNT_LENGTH = Byte.SIZE / 8;

	/**
	 * are ghost bubbles enabled?
	 */

	private byte ghostBubblesEnabled;
	private static final int GHOST_BUBBLES_ENABLED_OFFSET = NORMAL_BUBBLE_COUNT_OFFSET + NORMAL_BUBBLE_COUNT_LENGTH;
	private static final int GHOST_BUBBLES_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * are rainbow bubbles enabled?
	 */

	private byte rainbowBubblesEnabled;
	private static final int RAINBOW_BUBBLES_ENABLED_OFFSET = GHOST_BUBBLES_ENABLED_OFFSET + GHOST_BUBBLES_ENABLED_LENGTH;
	private static final int RAINBOW_BUBBLES_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * are blazing bubbles enabled?
	 */

	private byte blazingBubblesEnabled;
	private static final int BLAZING_BUBBLES_ENABLED_OFFSET = RAINBOW_BUBBLES_ENABLED_OFFSET + RAINBOW_BUBBLES_ENABLED_LENGTH;
	private static final int BLAZING_BUBBLES_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * are chain reactions enabled?
	 */

	private byte chainReactionsEnabled;
	private static final int CHAIN_REACTIONS_ENABLED_OFFSET = BLAZING_BUBBLES_ENABLED_OFFSET + BLAZING_BUBBLES_ENABLED_LENGTH;
	private static final int CHAIN_REACTIONS_ENABLED_LENGTH = Byte.SIZE / 8;


	/**
	 * is gravity enabled?
	 */

	private byte gravityEnabled;
	private static final int GRAVITY_ENABLED_OFFSET = CHAIN_REACTIONS_ENABLED_OFFSET + CHAIN_REACTIONS_ENABLED_LENGTH;
	private static final int GRAVITY_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * is wind enabled?
	 */

	private byte windEnabled;
	private static final int WIND_ENABLED_OFFSET = GRAVITY_ENABLED_OFFSET + GRAVITY_ENABLED_LENGTH;
	private static final int WIND_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * is the field flipped?
	 */

	private byte fieldFlipped;
	private static final int FIELD_FLIPPED_ENABLED_OFFSET = WIND_ENABLED_LENGTH + WIND_ENABLED_OFFSET;
	private static final int FIELD_FLIPPED_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * the game mode
	 */
	private byte gameMode;
	private static final int GAME_MODE_OFFSET = FIELD_FLIPPED_ENABLED_OFFSET + FIELD_FLIPPED_ENABLED_LENGTH;
	private static final int GAME_MODE_LENGTH = Byte.SIZE / 8;

	/**
	 * are teams enabled
	 */
	private byte teamsEnabled;
	private static final int TEAMS_ENABLED_OFFSET = GAME_MODE_OFFSET + GAME_MODE_LENGTH;
	private static final int TEAMS_ENABLED_LENGTH = Byte.SIZE / 8;

	/**
	 * Length of the game name
	 */
	private byte gameNameLength;
	private static final int GAME_NAME_LENGTH_OFFSET = TEAMS_ENABLED_OFFSET + TEAMS_ENABLED_LENGTH;
	private static final int GAME_NAME_LENGTH_LENGTH = Byte.SIZE / 8;

	/**
	 * Reason for rejection or something else we want to tell the client.
	 */
	private byte[] gameName;
	private static final int GAME_NAME_OFFSET = GAME_NAME_LENGTH_OFFSET + GAME_NAME_LENGTH_LENGTH;

	private static final int SIZE =
		Base.SIZE +
		PORT_LENGTH +
		CURRENT_PLAYER_COUNT_LENGTH +
		MAXIMUM_PLAYER_COUNT_LENGTH +
		NORMAL_BUBBLE_COUNT_LENGTH +
		GHOST_BUBBLES_ENABLED_LENGTH +
		RAINBOW_BUBBLES_ENABLED_LENGTH +
		BLAZING_BUBBLES_ENABLED_LENGTH +
		CHAIN_REACTIONS_ENABLED_LENGTH +
		GRAVITY_ENABLED_LENGTH +
		WIND_ENABLED_LENGTH +
		FIELD_FLIPPED_ENABLED_LENGTH +
		GAME_MODE_LENGTH +
		TEAMS_ENABLED_LENGTH +
		GAME_NAME_LENGTH_LENGTH;
	/*
	 * port					(int)		X
	 * current player count (int)		X	GS
	 * maximum player count (int)		X	GS
	 * normalbubblecount	(int)		X	GS
	 * ghostbubble			(boolean)	X	GS
	 * rainbow				(boolean)	X	GS
	 * blazingbubble		(boolean)	X	GS
	 * chainreactions		(boolean)	X	GS
	 * gravity				(boolean)	X	GS
	 * wind					(boolean)	X	GS
	 * flipped				(boolean)	X	GS
	 * gameMode				(enum)		X	GS
	 * teams				(boolean)	X	GS
	 * game name length		(int)		X	--
	 * game name			(string)	X	GS
	 */

	public GameListGameData() {
		super(true, TYPE_BYTES);
	}

	public GameListGameData(GameData gameData) {
		super(true, TYPE_BYTES);
		setPort(gameData.getGamePort());
		setCurrentPlayerCount((byte)gameData.getPlayerCount());
		setMaximumPlayerCount((byte)gameData.getGameSettings().getPlayerLimit());
		setNormalBubbleCount((byte)gameData.getGameSettings().getOrdinaryBubbleCount());
		setGhostBubbles(gameData.getGameSettings().getGhostBubbleProbability() != 0.0f);
		setRainbowBubbles(gameData.getGameSettings().getRainbowBubbleProbability() != 0.0f);
		setBlazingBubbles(gameData.getGameSettings().getBlazingBubbleProbability() != 0.0f);
		setChainReactions(gameData.getGameSettings().isChainReactions());
		setGravity(gameData.getGameSettings().isGravity());
		setWind(gameData.getGameSettings().isWind());
		setGameMode(gameData.getGameSettings().getMode());
		setGameName(gameData.getGameSettings().getName());
		setTeamsEnabled(gameData.getGameSettings().isTeamsEnabled());
		this.gameNameLength = (byte)gameData.getGameSettings().getName().length();

		/* ****************************
		 * TODO
		 * replace placeholder values in the following functions with
		 * actual values from game.getSettings() methods
		 * ****************************/
		setFieldFlipped(false);
	}

	public int getPort(){
		return port;
	}

	public void setPort(int port){
		this.port = port;
	}

	public byte getCurrentPlayerCount(){
		return currentPlayerCount;
	}

	public void setCurrentPlayerCount(byte playerCount){
		this.currentPlayerCount = playerCount;
	}

	public byte getMaximumPlayerCount(){
		return maximumPlayerCount;
	}

	public void setMaximumPlayerCount(byte playerCount){
		this.maximumPlayerCount = playerCount;
	}

	public byte getNormalBubbleCount(){
		return normalBubbleCount;
	}

	public void setNormalBubbleCount(byte bubbleCount){
		this.normalBubbleCount = bubbleCount;
	}

	public boolean getGravity() {
		return gravityEnabled != 0;
	}

	public void setGravity(boolean gravity) {
		this.gravityEnabled = (byte) (gravity ? 1 : 0);
	}

	public boolean getWind() {
		return windEnabled != 0;
	}

	public void setWind(boolean wind) {
		this.windEnabled = (byte) (wind ? 1 : 0);
	}

	public boolean getChainReactions() {
		return chainReactionsEnabled != 0;
	}

	public void setChainReactions(boolean chainReactions) {
		this.chainReactionsEnabled = (byte) (chainReactions ? 1 : 0);
	}

	public boolean getRainbowBubbles() {
		return rainbowBubblesEnabled != 0;
	}

	public void setRainbowBubbles(boolean rainbowBubbles) {
		this.rainbowBubblesEnabled = (byte) (rainbowBubbles ? 1 : 0);
	}

	public boolean getBlazingBubbles() {
		return blazingBubblesEnabled != 0;
	}

	public void setBlazingBubbles(boolean blazingBubbles) {
		this.blazingBubblesEnabled = (byte) (blazingBubbles ? 1 : 0);
	}

	public boolean getGhostBubbles() {
		return ghostBubblesEnabled != 0;
	}

	public void setGhostBubbles(boolean ghostBubbles) {
		this.ghostBubblesEnabled = (byte) (ghostBubbles ? 1 : 0);
	}

	public boolean getTeamsEnabled(){
		return teamsEnabled != 0;
	}

	public void setTeamsEnabled(boolean teamsEnabled){
		this.teamsEnabled = (byte) (teamsEnabled ? 1 : 0);
	}

	public boolean getFieldFlipped(){
		return fieldFlipped != 0;
	}

	public void setFieldFlipped(boolean fieldFlipped){
		this.fieldFlipped = (byte) (fieldFlipped ? 1 : 0);
	}

	public Game.Mode getGameMode(){
		return Game.Mode.fromType(gameMode);
	}

	public void setGameMode(Game.Mode mode){
		this.gameMode = mode.getType();
	}

	public byte[] getGameName() {
		return gameName;
	}

	public void setGameName(String name) {
		byte[] nameBytes = name.getBytes();

		gameNameLength = (byte) ((nameBytes.length > CreateGame.GAME_NAME_LENGTH_MAX) ? CreateGame.GAME_NAME_LENGTH_MAX : nameBytes.length);

		this.gameName = new byte[gameNameLength];
		System.arraycopy(nameBytes, 0, this.gameName, 0, gameNameLength);
	}

	@Override
	public void fill(byte[] buffer) {
		super.fill(buffer);
		port = Util.makeInt(buffer, PORT_OFFSET);
		currentPlayerCount = buffer[CURRENT_PLAYER_COUNT_OFFSET];
		maximumPlayerCount = buffer[MAXIMUM_PLAYER_COUNT_OFFSET];
		normalBubbleCount = buffer[NORMAL_BUBBLE_COUNT_OFFSET];
		ghostBubblesEnabled = buffer[GHOST_BUBBLES_ENABLED_OFFSET];
		rainbowBubblesEnabled = buffer[RAINBOW_BUBBLES_ENABLED_OFFSET];
		blazingBubblesEnabled = buffer[BLAZING_BUBBLES_ENABLED_OFFSET];
		chainReactionsEnabled = buffer[CHAIN_REACTIONS_ENABLED_OFFSET];
		gravityEnabled = buffer[GRAVITY_ENABLED_OFFSET];
		windEnabled = buffer[WIND_ENABLED_OFFSET];
		fieldFlipped = buffer[FIELD_FLIPPED_ENABLED_OFFSET];
		gameMode = buffer[GAME_MODE_OFFSET];
		teamsEnabled = buffer[TEAMS_ENABLED_OFFSET];
		gameNameLength = buffer[GAME_NAME_LENGTH_OFFSET];
		gameName = new byte[gameNameLength];
		Util.arraycopy(buffer, GAME_NAME_OFFSET, gameName, 0, gameNameLength);
	}

	@Override
	public void getBytes(byte[] buffer) {
		super.getBytes(buffer);
		Util.arraycopy(Util.makeBytes(port), 0, buffer, PORT_OFFSET);
		buffer[CURRENT_PLAYER_COUNT_OFFSET] = currentPlayerCount;
		buffer[MAXIMUM_PLAYER_COUNT_OFFSET] = maximumPlayerCount;
		buffer[NORMAL_BUBBLE_COUNT_OFFSET] = normalBubbleCount;
		buffer[GHOST_BUBBLES_ENABLED_OFFSET] = ghostBubblesEnabled;
		buffer[RAINBOW_BUBBLES_ENABLED_OFFSET] = rainbowBubblesEnabled;
		buffer[BLAZING_BUBBLES_ENABLED_OFFSET] = blazingBubblesEnabled;
		buffer[CHAIN_REACTIONS_ENABLED_OFFSET] = chainReactionsEnabled;
		buffer[GRAVITY_ENABLED_OFFSET] = gravityEnabled;
		buffer[WIND_ENABLED_OFFSET] = windEnabled;
		buffer[FIELD_FLIPPED_ENABLED_OFFSET] = fieldFlipped;
		buffer[GAME_MODE_OFFSET] = gameMode;
		buffer[TEAMS_ENABLED_OFFSET] = teamsEnabled;
		buffer[GAME_NAME_LENGTH_OFFSET] = gameNameLength;
		if(gameName != null) {
			Util.arraycopy(gameName, 0, buffer, GAME_NAME_OFFSET, gameNameLength);
		}
	}

	@Override
	public int getSize() {
		return SIZE + gameNameLength;
	}

	public static GameListGameData create(byte[] buffer, long receiveTime){
		GameListGameData packet = new GameListGameData();
		packet.fill(buffer);
		packet.setTimeStampReceive(receiveTime);
		return packet;
	}
}
